package com.naojump.android.twicca.picasa;

import static com.naojump.util.NUtils.hasChar;
import static com.naojump.util.NUtils.lconv;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;

import com.naojump.android.gdata.client.NClientLogin;
import com.naojump.android.gdata.util.NAuthenticationException;
import com.naojump.android.picasa.NPicasaWebAlbums;
import com.naojump.android.picasa.NUnsupportedMediaTypeException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * <h4>Picasa plug-in for twicca</h4> <br>
 * <br>
 * Operation Flow:<br>
 * <ol>
 * <li>check google account</li>
 * <li>choose album title</li>
 * <li>choose image size</li>
 * <ol>
 * 
 * @author Naoto Murata
 * @since 1
 * @version 9
 */
final public class NTwiccaPicasaPlugin extends Activity {

    /**
     * Event listener on select menu item, misc.
     * 
     * @author Naoto Murata
     * @since 1
     * @version 4
     */
    static interface OnNodeSelectListener {

        /**
         * Be called when item is selected.
         * 
         * @param node
         *            W3C DOM Node
         */
        public void onSelect(Node node);

    }

    /** DEBUG */
    private static final boolean DEBUG = false;

    /** TAG */
    private static final String TAG = "Picasa plug-in for twicca";

    /** Login info file pathname */
    private static final String LOGIN_FILE_PATHNAME = "login.dat";

    /** Request code: Login (0) */
    private static final int REQUEST_CODE_LOGIN = 0;

    /** Request code: Upload (1) */
    private static final int REQUEST_CODE_UPLOAD = 1;

    /**  */
    private static final String KEY_TX_OPTIONS_PREVIEW_VISIBILITY = "txOptionsPreview.visibility";

    /**  */
    private static final String KEY_VG_OPTIONS_VISIBILITY = "vgOptions.visibility";

    /** Options Preview */
    private TextView mTxOptionsPreview = null;

    /** Options */
    private ViewGroup mVgOptions = null;

    /** Email */
    private EditText mTxEmail = null;

    /** Password */
    private EditText mTxPasswd = null;

    /** Url format */
    private RadioGroup mRdUrlFormat = null;

    /** Save settings */
    private CheckBox mCxSaveSettings = null;

    /**
     * A part of Google ClientLogin status.<br>
     * This is used when sent correct email address and password.<br>
     */
    private NClientLogin.Status mStatus = null;

    /** Called when the activity is created at first. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DEBUG)
            Log.d(TAG, "onCreate");

        setContentView(R.layout.main);

        findViewById(R.id.go_picasaweb).setOnClickListener(
                new View.OnClickListener() {
                    @SuppressWarnings("synthetic-access")
                    @Override
                    public void onClick(View v) {
                        handleTxGoPicasawebOnClick();
                    }
                });
        mTxOptionsPreview = (TextView) findViewById(R.id.options_preview);
        mTxOptionsPreview.setOnClickListener(new View.OnClickListener() {
            @SuppressWarnings("synthetic-access")
            @Override
            public void onClick(View v) {
                handleTxOptionsPreviewOnClick();
            }
        });
        mVgOptions = (ViewGroup) findViewById(R.id.options);
        mTxEmail = (EditText) findViewById(R.id.email);
        mTxPasswd = (EditText) findViewById(R.id.password);
        mRdUrlFormat = (RadioGroup) findViewById(R.id.url_format);
        mCxSaveSettings = (CheckBox) findViewById(R.id.save_settings);
        findViewById(R.id.login_upload).setOnClickListener(
                new View.OnClickListener() {
                    @SuppressWarnings("synthetic-access")
                    @Override
                    public void onClick(View v) {
                        handleBtLoginUploadOnClick();
                    }
                });
        findViewById(R.id.cancel).setOnClickListener(
                new View.OnClickListener() {
                    @SuppressWarnings("synthetic-access")
                    @Override
                    public void onClick(View v) {
                        handleBtCancelOnClick();
                    }
                });

        loadLoginInfo();

        if (savedInstanceState != null) {
            Log.d(TAG, "savedInstanceState != null.");
            mTxOptionsPreview.setVisibility(savedInstanceState
                    .getInt(KEY_TX_OPTIONS_PREVIEW_VISIBILITY));
            mVgOptions.setVisibility(savedInstanceState
                    .getInt(KEY_VG_OPTIONS_VISIBILITY));
        }
    }

    /**
     * This is called before when the activity is destroying.
     * 
     * @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
     * @since 6
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (DEBUG)
            Log.d(TAG, "onSaveInstanceState");

        outState.putInt(KEY_TX_OPTIONS_PREVIEW_VISIBILITY, mTxOptionsPreview
                .getVisibility());
        outState.putInt(KEY_VG_OPTIONS_VISIBILITY, mVgOptions.getVisibility());
    }

    /**
     * Loads login info.
     */
    private void loadLoginInfo() {
        if (DEBUG)
            Log.d(TAG, "loadLoginInfo");

        InputStream in = null;
        try {
            Properties prop = new Properties();
            in = new BufferedInputStream(openFileInput(LOGIN_FILE_PATHNAME));
            prop.load(in);

            // Load properties.
            mTxEmail.setText(prop.getProperty("email", ""));
            mTxPasswd.setText(prop.getProperty("passwd", ""));
            ((RadioButton) mRdUrlFormat.findViewWithTag(prop.getProperty(
                    "urlfmt", "canonical"))).setChecked(true);
            mCxSaveSettings.setChecked(true);

            // Set options preview text.
            StringBuilder sb = new StringBuilder();
            sb.append(getString(R.string.click_to_edit_options)).append("\n\n");
            sb.append(getString(R.string.email)).append("\n  ");
            sb.append(prop.getProperty("email", "")).append("\n");
            sb.append(getString(R.string.url_format)).append("\n  ");
            sb.append(((RadioButton) findViewById(mRdUrlFormat
                    .getCheckedRadioButtonId())).getText());
            mTxOptionsPreview.setText(sb);

        } catch (IOException e) {
            handleTxOptionsPreviewOnClick();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                // Do nothing.
            }
        }
    }

    /**
     * Loads login info.
     * 
     * @return Login info
     */
    private Properties loadLoginInfoAsProperties() {
        if (DEBUG)
            Log.d(TAG, "loadLoginInfo");

        Properties prop = new Properties();
        InputStream in = null;
        try {
            in = new BufferedInputStream(openFileInput(LOGIN_FILE_PATHNAME));
            prop.load(in);
        } catch (IOException e) {
            handleTxOptionsPreviewOnClick();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                // Do nothing.
            }
        }
        return prop;
    }

    /**
     * Saves login info.
     */
    private void saveLoginInfo() {
        if (DEBUG)
            Log.d(TAG, "saveLoginInfo");

        if (mCxSaveSettings.isChecked()) {
            OutputStream out = null;
            try {
                Properties prop = new Properties();
                prop.setProperty("email", mTxEmail.getText().toString());
                prop.setProperty("passwd", mTxPasswd.getText().toString());
                prop.setProperty("urlfmt", findViewById(
                        mRdUrlFormat.getCheckedRadioButtonId()).getTag()
                        .toString());
                if (mStatus != null && hasChar(mStatus.getAuth())) {
                    prop.setProperty("token", mStatus.getAuth());
                }

                out = new BufferedOutputStream(openFileOutput(
                        LOGIN_FILE_PATHNAME, MODE_PRIVATE));
                prop.save(out, "");
            } catch (IOException e) {
                // Do nothing.
            } finally {
                try {
                    if (out != null) {
                        out.close();
                        out = null;
                    }
                } catch (IOException e) {
                    // Do nothing.
                }
            }
        } else {
            deleteFile(LOGIN_FILE_PATHNAME);
        }
    }

    /**
     * Login procedure
     * 
     * @param email
     *            Email address
     * @param password
     *            Password for user
     * @param auth
     *            Authentication token
     */
    private void login(String email, String password, String auth) {
        if (DEBUG)
            Log.d(TAG, "login");

        Intent newIntent = new Intent(this, NLoginActivity.class);
        newIntent.putExtra("email", email);
        newIntent.putExtra("password", password);
        newIntent.putExtra("auth", auth);

        startActivityForResult(newIntent, REQUEST_CODE_LOGIN);
    }

    /**
     * Upload procedure
     */
    private void upload() {
        if (DEBUG)
            Log.d(TAG, "upload");

        final String email = mTxEmail.getText().toString().trim();

        final NPicasaWebAlbums lh2 = new NPicasaWebAlbums(email, mStatus);
        final Intent intent = getIntent();

        final Cursor c = getContentResolver().query(
                intent.getData(),
                new String[] { MediaStore.Images.Media.SIZE,
                        MediaStore.Images.Media.TITLE,
                        MediaStore.Images.Media.MIME_TYPE, }, null, null, null);
        c.moveToFirst();

        // Select album, and upload.
        try {
            selectAlbum(lh2, new OnNodeSelectListener() {
                @SuppressWarnings("synthetic-access")
                @Override
                public void onSelect(Node node) {

                    String albumId = "";
                    String albumUri = "";
                    String albumTitle = "";

                    for (Node child : lconv(node.getChildNodes())) {
                        NamedNodeMap attrs = child.getAttributes();
                        if ("link".equals(child.getNodeName())
                                && "alternate".equals(attrs.getNamedItem("rel")
                                        .getNodeValue())
                                && "text/html".equals(attrs
                                        .getNamedItem("type").getNodeValue())) {
                            albumUri = attrs.getNamedItem("href")
                                    .getNodeValue();
                        }
                        if ("gphoto:id".equals(child.getNodeName())
                                && (child.getFirstChild() != null)) {
                            albumId = child.getFirstChild().getNodeValue();
                        }
                        if ("title".equals(child.getNodeName())
                                && (child.getFirstChild() != null)) {
                            albumTitle = child.getFirstChild().getNodeValue();
                        }
                    }

                    long size = c.getLong(0);
                    String title = c.getString(1);
                    String mimeType = c.getString(2);

                    // Check extension is on the title.
                    // And when no extension exists, apply it.
                    if (hasChar(title) && hasChar(mimeType)) {
                        for (String[] mimeAndExts : NPicasaWebAlbums.MIME_TYPE_EXTENSIONS) {
                            String mime = mimeAndExts[0];
                            if (mimeType.equalsIgnoreCase(mime)) {
                                boolean hasNoExtension = true;
                                for (int i = 1; i < mimeAndExts.length; i++) {
                                    if (title.toLowerCase().endsWith(
                                            mimeAndExts[i].toLowerCase())) {
                                        hasNoExtension = false;
                                        break;
                                    }
                                }
                                if (hasNoExtension) {
                                    title += mimeAndExts[1];
                                }
                                break;
                            }
                        }
                    }

                    Intent newIntent = new Intent(Consts.INTENT_ACTION_UPLOAD);
                    newIntent
                            .setDataAndType(intent.getData(), intent.getType());
                    newIntent.putExtra("status", mStatus);
                    newIntent.putExtra("userId", email);
                    newIntent.putExtra("albumId", albumId);
                    newIntent.putExtra("albumUri", albumUri);
                    newIntent.putExtra("albumTitle", albumTitle);
                    newIntent.putExtra("size", size);
                    newIntent.putExtra("title", title);
                    if (mRdUrlFormat.getCheckedRadioButtonId() == R.id.album_url) {
                        newIntent.putExtra("background", true);
                    }

                    startActivityForResult(newIntent, REQUEST_CODE_UPLOAD);
                }
            });
        } catch (NAuthenticationException e) {
            handleNoAuthentication(e);
        } catch (IOException e) {
            handleNetworkError(e);
        }
    }

    /**
     * Process album selection
     * 
     * @param lh2
     *            Instantiated NPicasaWebAlbums object
     * @param lsnr
     *            Listener when an item is selected
     * @throws NAuthenticationException
     *             Throws when not authenticated.
     * @throws IOException
     *             Throws when network error has occurred.
     */
    private void selectAlbum(NPicasaWebAlbums lh2,
            final OnNodeSelectListener lsnr) throws NAuthenticationException,
            IOException {
        if (DEBUG)
            Log.d(TAG, "selectAlbum");

        final List<Map<String, ?>> list = new ArrayList<Map<String, ?>>();

        // Public albums only (by security reason).
        for (Node album : lh2.getAlbums(NPicasaWebAlbums.ACCESS_PUBLIC)) {

            Map<String, Object> map = new HashMap<String, Object>();
            map.put("node", album);

            for (Node child : lconv(album.getChildNodes())) {
                if ("title".equals(child.getNodeName())
                        && (child.getFirstChild() != null)) {
                    map.put("title", child.getFirstChild().getNodeValue());
                }
                if ("summary".equals(child.getNodeName())
                        && (child.getFirstChild() != null)) {
                    map.put("summary", child.getFirstChild().getNodeValue());
                }
                if ("rights".equals(child.getNodeName())
                        && (child.getFirstChild() != null)) {
                    map.put("rights", child.getFirstChild().getNodeValue());
                }
            }

            list.add(map);
        }

        // Select an public album if public album(s) exists.
        // Or create a public album and select if public album(s) NOT exists.
        if (!(list.isEmpty())) {
            SimpleAdapter a = new SimpleAdapter(this, list, R.layout.album,
                    new String[] { "title", "summary", "rights" }, new int[] {
                            R.id.album_title, R.id.album_summary,
                            R.id.album_rights });

            DialogInterface.OnClickListener onSelectListener = new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (DEBUG)
                        Log.d(TAG, "selectAlbum#onClick");

                    Map<String, ?> item = list.get(which);
                    lsnr.onSelect((Node) item.get("node"));
                    dialog.dismiss();
                }
            };

            new AlertDialog.Builder(this).setTitle(R.string.select_album)
                    .setAdapter(a, onSelectListener).show();

        } else {
            try {
                HttpResponse res = lh2.createNewPublicAlbum();
                if (res.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) {
                    selectAlbum(lh2, lsnr);
                } else {
                    if (DEBUG)
                        Log
                                .e(TAG,
                                        "selectAlbum: Failed to create new public album.");

                    Toast.makeText(this,
                            "Error: failed to create new public album.",
                            Toast.LENGTH_LONG).show();
                }
            } catch (NAuthenticationException e) {
                handleNoAuthentication(e);
            }
        }
    }

    /**
     * @see android.app.Activity#onActivityResult(int, int,
     *      android.content.Intent)
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (DEBUG)
            Log.d(TAG, "onActivityResult");

        switch (requestCode) {
        case REQUEST_CODE_LOGIN: {
            handleOnLoginActivityResult(data);
        }
            break;
        case REQUEST_CODE_UPLOAD: {
            handleOnUploadActivityResult(resultCode, data);
        }
            break;
        }
    }

    /**
     * Handles result from LoginActivity.
     * 
     * @param data
     *            Log-in status
     * @see android.app.Activity#onActivityResult(int, int,
     *      android.content.Intent)
     */
    private void handleOnLoginActivityResult(Intent data) {
        if (DEBUG)
            Log.d(TAG, "handleOnLoginActivityResult");

        if (data == null) {
            return;
        }

        mStatus = (NClientLogin.Status) data.getExtras().get("status");
        if (mStatus != null) {
            if (mStatus.isAuthenticated()) {
                Log.i(TAG, "Authenticated.");

                saveLoginInfo();
                upload();
            } else if (mStatus instanceof NClientLogin.CaptchaChallenge) {
                Log.i(TAG, "Required CAPTCHA challenge.");
                // TODO CAPTCHA challenge
                showAlertDialog(R.string.not_authed);
            } else {
                Log.w(TAG, "Unauthorized.");
                showAlertDialog(R.string.not_authed);
            }
        } else if (data.getExtras().get("exception") != null) {
            Exception e = (Exception) data.getExtras().get("exception");
            if (e instanceof FileNotFoundException) {
                handleFileNotFound((FileNotFoundException) e);
            } else if (e instanceof IOException) {
                handleNetworkError((IOException) e);
            } else if (e instanceof NAuthenticationException) {
                handleNoAuthentication((NAuthenticationException) e);
            } else if (e instanceof NUnsupportedMediaTypeException) {
                handleUnsupportedMediaType((NUnsupportedMediaTypeException) e);
            }
        }
    }

    /**
     * Handles result from UploadActivity.
     * 
     * @param resultCode
     *            Status code
     * @param data
     *            Log-in status
     * @see android.app.Activity#onActivityResult(int, int,
     *      android.content.Intent)
     */
    private void handleOnUploadActivityResult(int resultCode, Intent data) {
        if (DEBUG)
            Log.d(TAG, "handleOnUploadActivityResult");

        if (data == null) {
            return;
        }

        switch (resultCode) {
        case HttpStatus.SC_OK:
        case HttpStatus.SC_CREATED: {
            Log.i(TAG, "Uploaded.");

            String url = "";
            switch (mRdUrlFormat.getCheckedRadioButtonId()) {
            case R.id.content_url:
                url = data.getExtras().getString("content");
                break;
            case R.id.alternate_url:
                url = data.getExtras().getString("alternate");
                break;
            case R.id.canonical_url:
                url = data.getExtras().getString("canonical");
                break;
            case R.id.album_url:
                url = data.getExtras().getString("albumUri");
                break;
            }

            setResultAndFinish(RESULT_OK, getIntent(), url);
        }
            break;
        default: {
            Log.w(TAG, getString(R.string.upload_failed) + " code="
                    + resultCode);
            showAlertDialog(getString(R.string.upload_failed) + " code="
                    + resultCode);
        }
            break;
        }
        if (data.getExtras().get("exception") != null) {
            Exception e = (Exception) data.getExtras().get("exception");
            if (e instanceof FileNotFoundException) {
                handleFileNotFound((FileNotFoundException) e);
            } else if (e instanceof IOException) {
                handleNetworkError((IOException) e);
            } else if (e instanceof NAuthenticationException) {
                handleNoAuthentication((NAuthenticationException) e);
            } else if (e instanceof NUnsupportedMediaTypeException) {
                handleUnsupportedMediaType((NUnsupportedMediaTypeException) e);
            }
        }
    }

    /**
     * Handles txNewAcct on click.
     */
    private void handleTxGoPicasawebOnClick() {
        if (DEBUG)
            Log.d(TAG, "handleTxGoPicasawebOnClick");

        // Open Picasa Web Albums in browser.
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri
                .parse(Consts.WEB_ALBUMS_LOGIN_URL));
        startActivity(intent);
    }

    /**
     * Handles txOptionsPreview on click.
     */
    private void handleTxOptionsPreviewOnClick() {
        if (DEBUG)
            Log.d(TAG, "handleTxOptionsPreviewOnClick");

        // Hide options preview and show options view-group.
        mTxOptionsPreview.setVisibility(View.GONE);
        mVgOptions.setVisibility(View.VISIBLE);
    }

    /**
     * Handles btLoginUpload on click.
     */
    private void handleBtLoginUploadOnClick() {
        if (DEBUG)
            Log.d(TAG, "handleBtLoginUploadOnClick");

        String email = mTxEmail.getText().toString().trim();
        String password = mTxPasswd.getText().toString().trim();

        if (hasChar(email) && hasChar(password)) {
            Properties prop = loadLoginInfoAsProperties();
            if (email.equals(prop.getProperty("email"))
                    && password.equals(prop.getProperty("passwd"))) {

                String token = prop.getProperty("token");
                login(email, password, token);
            } else {
                login(email, password, "");
            }
        } else {
            Toast.makeText(this, R.string.input_values, Toast.LENGTH_SHORT)
                    .show();
        }
    }

    /**
     * Handles btCancel on click.
     */
    private void handleBtCancelOnClick() {
        if (DEBUG)
            Log.d(TAG, "handleBtCancelOnClick");

        finish();
    }

    /**
     * Handles file not found.
     * 
     * @param e
     *            Exception
     */
    void handleFileNotFound(FileNotFoundException e) {
        if (DEBUG)
            Log.w(TAG, "File not found", e);

        showAlertDialog(R.string.file_not_found);
    }

    /**
     * Handles network error.
     * 
     * @param e
     *            Exception
     */
    void handleNetworkError(IOException e) {
        if (DEBUG)
            Log.w(TAG, "Network error", e);

        showAlertDialog(R.string.network_error);
    }

    /**
     * Handles authentication error.
     * 
     * @param e
     *            Exception
     */
    void handleNoAuthentication(NAuthenticationException e) {
        if (DEBUG)
            Log.w(TAG, "Not authenticated", e);

        showAlertDialog(R.string.not_authed);
    }

    /**
     * Handles unsupported media type error.
     * 
     * @param e
     *            Exception
     */
    void handleUnsupportedMediaType(NUnsupportedMediaTypeException e) {
        if (DEBUG)
            Log.w(TAG, "Sorry, unsupported media type now...", e);

        showAlertDialog(R.string.unsupported_type);
    }

    /**
     * Sets result and finish.
     * 
     * @param resultCode
     *            Result code
     * @param data
     *            Return value.
     * @param url
     *            URL to uploaded image
     */
    void setResultAndFinish(int resultCode, Intent data, String url) {
        if (DEBUG)
            Log.d(TAG, "setResultAndFinish");

        if (url != null) {
            data.setData(Uri.parse(url));
            setResult(resultCode, data);
        }
        finish();
    }

    /**
     * Shows alert dialog.
     * 
     * @param resId
     *            Resource id for the string
     */
    void showAlertDialog(int resId) {
        new AlertDialog.Builder(this).setTitle(R.string.app_name).setMessage(
                resId).show();
    }

    /**
     * Shows alert dialog.
     * 
     * @param msg
     *            Message to display
     */
    void showAlertDialog(String msg) {
        new AlertDialog.Builder(this).setTitle(R.string.app_name).setMessage(
                msg).show();
    }

}
